// SFrame.cpp
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
 
#include <stdlib.h>

#include "sframe.h"
#include "twl_string.h"
#include "file_utils.h"
#include "cpp_manip.h"


const char* NONAME = "untitled";
const int MAX_CAPTION = 128, MAX_FILES_USED = 9, MAX_VIEWS_SHOWN = 9,
          MAX_BREAKPOINTS = 35, LINESIZE = 256;

const int DBLCLK_DELAY = 120;

char *filter =
  "C++ source (*.CPP)|*.cpp|"
  "C++ header (*.H)|*.h|"
  "Text Files (*.TXT)|*.txt|"
  "All Files (*.*)|*.*|";

const int Q2K_CONFIRM=4, Q2K_CONFIRM_RESULT=648, Q2K_MESSAGE=1,
          Q2K_ERROR=2, Q2K_RECONNECT=3, Q2K_BRING_TO_FRONT = 6;
const int MESSAGE_GENERAL = 0, MESSAGE_FINISHED = 2, MESSAGE_BREAKPOINT = 1,
          MESSAGE_ERROR = 3, MESSAGE_SHUT_DOWN = 4;


string itos(int); //*temp
bool prompt_for_str(char* caption, char* text, string& s); // in debug_windows.cpp

bool is_cpp_keyword(string word); // in scintillawin.cpp

ScintillaWin* SFrame::current_view()
{
    return gMainFrame->view();
}

void SFrame::stop_marker(bool on_off,int line)
{
    if (on_off) {
      m_exec_line = line;
      m_sci->add_marker(EXEC_POINT,line);
    } else {
      if (m_exec_line != -1) m_sci->delete_marker(EXEC_POINT,m_exec_line);      
      m_exec_line = -1;
    }
}

bool SFrame::go_to_filepos()
{
  string file,buff;
  int line = m_ucw->get_filepos(file);
  // this hack ignores any errors which originated at the interactive prompt
  if (file != "CON") {
    set_filename(file);
    m_sci->goto_line(line);
    stop_marker(true,line);
	return true;
  } else
    return false;
}

void SFrame::error(string msg)
{
  message(msg.c_str(),MSG_ERROR);
}

void SFrame::warning(string msg)
{
  message(msg.c_str());
}

void SFrame::handle_trouble(string msg)
{
  string buff;
  m_ucw->get_err(buff);
  msg = msg + " " + buff;
  error(msg);
}

void SFrame::set_state(UseState state)
{
	if (state != BREAK) {
      stop_marker(false);
    }
	m_state = state;
    if (m_sci != NULL) {
        update_caption();
        if (state == BREAK || state == BROKEN) {
          to_foreground();
          set_focus();
        }
    }
}


bool Position::action()
{
  gMainFrame->open_at(this);
  return true;
}


static CallStackWindow* gCSW;
static LocalVarsWindow* gLVW;
static WatchVarsWindow* gWVW;

void update_debug_windows(ListWindow* excl)
{
   if (excl != gCSW) gCSW->update();
   if (excl != gLVW) gLVW->update();
   if (excl != gWVW) gWVW->update();
}

void show_debug_window(ListWindow* w)
{
  w->show(1);
  w->on_top();
  w->update();
}

void SFrame::do_show_watch(int)
{
 show_debug_window(gWVW);
}

void SFrame::do_show_locals(int)
{
 show_debug_window(gLVW);
}

void SFrame::do_show_call_stack(int)
{
 show_debug_window(gCSW);
}

void SFrame::do_update(int)
{
  // attempt to reconnect to UCW
if (! m_debug_windows->visible())
  m_debug_windows->show();
else
  m_debug_windows->hide();
}

int  SFrame::handle_user(long lparam, long wparam)
{
// *SJD* Handles messages from UCW
   switch(lparam) {
   case Q2K_MESSAGE: // was an information message
	   switch(wparam) {
       case MESSAGE_ERROR:
       case MESSAGE_BREAKPOINT:
           set_state(wparam==MESSAGE_BREAKPOINT ? BREAK : BROKEN);
           go_to_filepos();
	   if (! m_debug_windows->visible()) m_debug_windows->show();
           update_debug_windows(NULL);
           if (wparam==MESSAGE_ERROR) handle_trouble("");
           break;
       case MESSAGE_FINISHED:
	   m_debug_windows->hide();
	   set_state(READY);
	   break;
       case MESSAGE_GENERAL:    set_state(RUNNING);  break;  // ??
       case MESSAGE_SHUT_DOWN:
           set_state(FINISHED);
           break;
	   }
   break;
   case Q2K_ERROR: 
     if (go_to_filepos()) 
        handle_trouble("");
   break;
   case Q2K_BRING_TO_FRONT:
//       TWin((Handle)wparam).on_top();
	   break;
   case Q2K_RECONNECT: // please reconnect
       if (m_do_connect) {
         m_ucw->connect();
         m_ucw->init(handle());
       }
   break;
   case Q2K_CONFIRM: // please confirm
     set_state(READY);
     return Q2K_CONFIRM_RESULT;
 }
 return 0;
}

bool SFrame::open_at(Position* pos)
{
 bool res = set_filename(pos->file);
 if (! res) return false;
 if (pos->line != -1) m_sci->goto_line(pos->line); 
 set_focus();
 return true;
}

void SFrame::update_caption()
{
 char buff[MAX_CAPTION];
 bool changed = m_sci->modified();
 char *state_str;
 switch(m_state) {
 case BREAK:   state_str = "[break]";   break;
 case RUNNING: state_str = "[run]";     break;
 case BROKEN:  state_str = "[crashed]"; break;
 case READY:   state_str = "[ready]";   break;
 default: state_str = ""; break;
 }
 sprintf(buff,
	 "%c%s - USE %s",(changed ? '*' : ' '),m_path.c_str(),state_str);
 set_text(buff);
}

void SFrame::set_current_view(const char* filename, ScintillaWin* sci)
{
  if (sci == NULL) {
     set_text("null view!");
     return;
  }
  m_path = filename;
  m_views_open->add_item(filename,sci);
  update_caption();
  // hide the previous scintilla....
  m_sci->show(0);
  m_sci = sci;
  // and make the new one our current client
  set_client(m_sci);
  m_sci->set_focus();
  if (m_sci->get_line_numbers() != m_show_line_nos)
     m_sci->set_line_numbers(m_show_line_nos);
  // shake the frame to make it wake-up (really shd
  // be part of set_client())
  size();
  update();
}

void SFrame::set_selected_view()
{
    set_current_view(m_views_open->current_file(),
          (ScintillaWin*)m_views_open->current_data());
}

bool SFrame::set_filename(const char *filename)
{
     if (filename == m_path) return true;
     m_untitled = string(filename) == NONAME;
     string file = canonical_form(m_cwd,filename);     
     if (m_untitled) {
         m_path = filename;		 
         update_caption();
      	 m_views_open->add_item(file.c_str(),m_sci);         
         return true;
     }

     // do we already have an open view of this file?
     if (m_views_open->match(file.c_str())) {
        m_views_open->select(1);
        set_selected_view();
        return true;
     } else // load file as usual...
     if (file_exists(file)) {
       create_scintilla();
       m_views_open->add_item(file.c_str(),m_sci);
       load_file(file.c_str());
	   m_modify_response = RESET;
	   m_sci->modified(false);
       m_path = file;
	   if (m_working_dir == "") m_working_dir = get_path(m_path);
       update_caption();
       m_files_used->add_item(file.c_str());
       m_sci->set_focus();
       if (m_sci->get_line_numbers() != m_show_line_nos)
           m_sci->set_line_numbers(m_show_line_nos);       

       // note the convention for m_cwd; trailing slash!
       m_cwd = get_path(m_path) + "\\";
       m_views_open->current_dir(m_cwd.c_str());
       m_files_used->current_dir(m_cwd.c_str());
       return true;
     } else {
       error("Cannot open "  + file);
       return false;
     }
}

bool SFrame::load_file(const char* file)
{
    return m_sci->load_file(file,0);
}

void SFrame::do_open(int)
{
   if (m_of->go()) {
     set_filename(m_of->file_name());     
   // *fix 0.5.1 The file open box must remember what we opened last...
     m_of->initial_dir(m_working_dir.c_str());

   }
}

void SFrame::do_save(int)
{
  if (m_untitled) {
      	do_save_as(0);      	
  }
  else {
    m_sci->save_file(m_path.c_str());
    set_modified(false);
  }
}

void SFrame::do_save_as(int)
{
   TSaveFile sf(this,"Save As",filter);
   sf.file_name(m_path.c_str());
   if (sf.go()) {
     m_sci->save_file(sf.file_name());
     set_filename(sf.file_name());
     set_modified(false);
     m_untitled = false; 
   }
}

void SFrame::do_reload(int)
{
  m_sci->clear_all();
  load_file(m_path.c_str());
}

void SFrame::do_insert_file(int)
{
  if (m_of->go()) 
    m_sci->load_file(m_of->file_name(),m_sci->pos());
}

void SFrame::do_copy_file(int)
{
  string file;
  if (prompt_for_str("File to receive Selection","File",file))
    m_sci->save_file(file.c_str(),1L);
}

void SFrame::do_operate_on_selection(int)
{
  string cmd;
  if (prompt_for_str("Command operating on Selection","Command",cmd)) {
    string f1 = "$tmpF01";
    string f2 = "$tmpF02";
    m_sci->save_file(f1.c_str(),1L);
    string cmdline = cmd + " < " + f1 + " > " + f2;
    system(cmdline.c_str());
    m_sci->load_file(f2.c_str(),1L);
  }
}

static char mFindStr[80];

void SFrame::do_find(int)
{
 string s;
 m_sci->current_word(s);
 strcpy(mFindStr,s.c_str());
 TModalLayout (this,"Find Text")
   << Field("Text",mFindStr,0);

 if (TModalLayout::success()) {
   if (! m_sci->find(mFindStr,0))
     warning("Cannot find text " + string(mFindStr));
 }
}

void SFrame::do_find_next(int)
{
  m_sci->find(mFindStr,0);
}

// note: these two guys really shd toggle the menu checked state!
void SFrame::do_line_nos(int)
{
  m_show_line_nos = ! m_show_line_nos;
  m_sci->set_line_numbers(m_show_line_nos);
}

void SFrame::do_short_filenames(int)
{
  bool short_filenames = m_files_used->strip_cwd();
  short_filenames = ! short_filenames;
  m_views_open->strip_cwd(short_filenames);
  m_files_used->strip_cwd(short_filenames);
}

void SFrame::do_match_brace(int)
{
 long bpos = m_sci->matching_brace();
 if (bpos > 0) m_sci->goto_position(bpos);
}

void SFrame::do_goto_line(int)
{
    int line = m_sci->current_line();
    TModalLayout (this,"Enter Line Number")
     << Field("Line",line,0);

    if (TModalLayout::success()) {
      m_sci->goto_line(line);
    }
}

void SFrame::do_toggle_marker(int)
{
 m_sci->add_marker(BOOKMARK,true);
}

void SFrame::do_goto_next_marker(int)
{
 m_sci->next_marker(BOOKMARK,true);  // wuz true
}

// try to find the corresponding header to this source file, and vice versa...
void SFrame::do_swap_header(int)
{
 string file, ext = get_extension(m_path);
 if (is_cpp_source_ext(ext))
   file = append_extensions_until_found(get_filename(m_path),header_extensions());
 else
 if (is_cpp_header_ext(ext))
   file = append_extensions_until_found(get_filename(m_path),source_extensions());
 else {
    error("Not a C/C++ file");
    return;
 }
 if (file=="") {
    error("corresponding source/header file not found");
    return;
 }
 set_filename(file);
}

void SFrame::do_select_function(int)
{
   long l1,l2;
   if (function_extent(m_sci,l1,l2,AS_POS))
       m_sci->set_selection(l1,l2);
   else error("Not inside a function");
}

// try to select the 2nd view in the list;
// this will automatically swap the view order!
void SFrame::do_swap_top(int)
{
  if (m_views_open->select(2))
    set_selected_view();
}

void SFrame::do_close_view(int)
{
  //* ask if user wants to save
  if (m_sci->modified()
    && message("Do you want to save these changes?",MSG_QUERY))
     do_save(0);

  ScintillaWin* current_sci = m_sci;
  do_swap_top(0);
  m_views_open->remove(2);
  current_sci->close();
}

void SFrame::do_windows(int)
{

}

void SFrame::do_about(int)
{
    TModalLayout(this,"About USE", ML_OK)
      << "UnderC Scripting Environment" << Next
      << "Steve Donovan, 2003" << Next << Next
	  << "Built with Scintilla (www.scintilla.org)" << Next;
}

void SFrame::do_files_list(int)
{
}

void SFrame::do_run_underc(int)
{
 if (m_state == FINISHED) {
	 if (! UCW::launch()) 
		 message("Cannot find UnderC");
 } else message("UnderC is already running");
}


void SFrame::do_run(int)
{
    if (can_run()) {
		if (compile_current_file(false)) {
			if (m_prompt_parm) do_parameters(0);
			m_ide->set_working_dir(m_working_dir);
            m_ide->run(m_parameters);
		}
    }
}

class Hourglass {
    TEventWindow* m_form;
public:
    Hourglass(TEventWindow* w)
        : m_form(w)
    {
       m_form->cursor(HOURGLASS);
    }

    ~Hourglass()
    {
        m_form->cursor(RESTORE);
    }
};

bool SFrame::compile_current_file(bool explicit_cmd)
{
  // save if necessary: three scenarios, depending on m_modify_response
  // (1) SAVE:  if modified, then save and recompile
  // (2) RESET: save and recompile anyway, but reset to SAVE
  //     (we use this to force compilation initially)
  // (3) WAIT:  don't compile if called implicitly from do_run();
    stop_marker(false);
    bool modified = m_sci->modified();
    // an explicit compilation request overrides WAIT
    if (explicit_cmd && m_modify_response == WAIT) m_modify_response = SAVE;
    if ((modified || m_modify_response == RESET) && m_modify_response != WAIT) {
       if (modified) do_save(0);
       if (m_modify_response==RESET) m_modify_response = SAVE;
       
    // attempt to compile
      Hourglass here(this);
      if (m_ide->compile(m_path) != OK) {
		 if (go_to_filepos()) {
            handle_trouble("");  
	        return false;
         } 
      }
   
    // and update the breakpoints, if any
      int break_list[MAX_BREAKPOINTS];
      int n = m_sci->fetch_markers(BREAKPOINT,break_list);
      if (n > 0) m_ide->breakpoint_list(m_path,break_list,n);
   }
   return true;
}

void SFrame::do_compile(int id)
{
  if (can_run()) compile_current_file(true);
}

void SFrame::do_compile_function(int)
{
   long l1,l2;
   if (function_extent(m_sci,l1,l2,AS_LINE)) {
       char* buff = m_sci->get_text_lines(l1,l2);      
       if (m_ucw->exec_with_pos(buff,m_path,l1) != OK) {
          go_to_filepos();
          handle_trouble("");  
	      return;
       }
       m_modify_response = WAIT;
       delete buff;
   } else message("Not inside a function");
}

void SFrame::do_parameters(int)
{
   TModalLayout (this,"Command-line Parameters")
     << Width(150) << SField("parameters",m_parameters)
     << Width(150) << SField("working dir",m_working_dir)
	 << Width(220) << Checkbox("Prompt before each run",m_prompt_parm);

    if (TModalLayout::success())
      m_ucw->exec("#args " + m_parameters);
}

void SFrame::do_insert_watch_var(int)
{
 gWVW->attempt_insert();
}

string extract_quote(char open_quote, char close_quote, string s)
{
  int l1 = s.find(open_quote);
  if (l1 != string::npos) { // there was a quote in this line
    int l2;
    s = s.substr(l1+1,string::npos);  // line after the quote
    l2 = s.find(close_quote);
    s = s.substr(0,l2);
    return s;
  } else
    return "";  // no can do
}

void SFrame::do_open_document(int)
{
  char buff[LINESIZE];  
  m_sci->get_current_line(buff,sizeof(buff));
  // look for quoted strings, which we try to open in the same directory
  // as this file
  string s = extract_quote('\"','\"',buff);
  if (s != "") { 
      set_filename(canonical_form(get_path(m_path)+"\\", s));
      return;
  }
  // otherwise, files in angle brackets are assumed to be include files
  s = extract_quote('<','>',buff);
  if (s != "") { 
      string res;
      if (m_ucw->expand_include_file(s,res))
        set_filename(res);
  }
}

void SFrame::do_expand(int)
{
    char buff[LINESIZE];
    string result;
    m_sci->get_current_line(buff,LINESIZE);
    m_ucw->expand_macro(buff,result);
    int line = m_sci->current_line();
    m_sci->set_selection(m_sci->get_pos_of_line_start(line),
                         m_sci->get_pos_of_line_end(line));
    m_sci->replace_selection(result.c_str());
}

void SFrame::do_goto_cursor(int)
{
    if (can_run()) {
      int line = m_sci->current_line();
      m_ide->set_breakpoint(m_path,line,false);
      m_ide->run("");
    }
}

void SFrame::do_toggle_breakpoint(int)
{
  int line = m_sci->current_line();
  int res = m_ide->set_breakpoint(m_path,line,true);
  if (res == MODULE_NOT_FOUND) {
    message("Please compile this file first");
    //do_compile(0);
	return;
  }
  if (res == OK)
      m_sci->add_marker(BREAKPOINT,true);
}

bool SFrame::can_run()
{
    if (m_state == BROKEN) {
        message("Program has crashed.\r\nPlease stop before continuing");
        return false;
    } else 
    if (m_state == FINISHED) {
        message("UnderC is not running");
        return false;
    } else
    return true; 
}

void SFrame::do_single_step(int)
{
  if (can_run())
    m_ide->single_step(false);
}

void SFrame::do_step_over(int)
{
  if (can_run())
    m_ide->single_step(true);
}

void SFrame::do_stop(int)
{
    m_ide->stop_program();
}

bool SFrame::set_modified(bool yesno)
{
  if (m_sci->modified(yesno)) update_caption();
  return true;
}

class Selection: public ItemObject {
    ScintillaWin* m_sci;
    long m_start, m_end;
public:
    Selection(ScintillaWin* sci, long s, long e)
        : m_sci(sci),m_start(s),m_end(e)
    {}

    bool action()
    {
     m_sci->set_selection(m_start,m_end);
     return true;
    }
};

void SFrame::on_double_click()
{
    if (m_sci->style_matches(CURRENT_POS, C_PREPROCESSOR | C_COMMENT))
       do_open_document(0);
    else {
      long this_p = m_sci->pos();
      long that_p = m_sci->matching_brace();
      if (that_p > 0) {
          if (that_p > this_p) {
              that_p++;
              this_p--;
          }
          delay_action(DBLCLK_DELAY, new Selection(m_sci,this_p,that_p));
      }
    }
}

void SFrame::delay_action(int msec, ItemObject* obj)
{
  m_delayed_op = obj;
  create_timer(msec);
}

void SFrame::timer()
{
  m_delayed_op->action();
  kill_timer();
}

bool SFrame::command(int id, int code)
{
  if (m_files_used->handle(id)) {
      set_filename(m_files_used->current_file());
      return true;
  } else
  if (m_views_open->handle(id)) {
      set_selected_view();
      return true;
  }
  return false;
}

// generally Scintilla notifications will come through here,
// altho note the important exception SCEN_CHANGE above.
int SFrame::notify(int id, void* ph)
{
 SCNotification *pn = (SCNotification*)ph;
 char buff[60];
 if (! pn) return 0;
 switch (pn->nmhdr.code) {
 case SCN_UPDATEUI: { // selection etc has changed; update position
    int line = m_sci->current_line();
    sprintf(buff,"ln %3d, col %2d",line,m_sci->current_column());
    set_status_text(0,buff);    
//    sprintf(buff,"%d %d",m_sci->get_fold_level(line),m_sci->blank_line(line));
	sprintf(buff,"%d",m_sci->pos());
    set_status_text(1,buff);
  }  break;
  case SCN_MODIFIED:
   set_modified(true);
   break;
  case SCN_DOUBLECLICK: 
   on_double_click();
   break;
  case SCN_DWELLSTART: {   
	// try to put up a call tip, but only if the mouse is near some text and we're connected.
	  if (m_state != FINISHED && pn->position != -1) { 
	    string word;
	    m_sci->current_word(word, pn->position);
		// if there's a word which is not a C++ keyword at the mouse cursor...
	    if (word.size() > 0 && ! is_cpp_keyword(word)) {
         string res;
		 //m_sci->show_call_tip(pn->position,word.c_str());
		 //return 1;

		 // attempt to evaluate this word
         if (m_ucw->eval(word,res,false)) {
            set_status_text(2,i2a(pn->position));
	        m_sci->show_call_tip(pn->position,res.c_str()); 
		 }
		}
	  }
   }
   break;
  case SCN_DWELLEND:
	m_sci->cancel_call_tip();
	break;

 }
 return 1;
}

void SFrame::on_close()
{
  m_form_pos->write();
  m_files_used->write();
  m_debug_windows->save();
}

bool SFrame::query_close()
{
 if (! m_sci->modified()) return true;
 if (message("Save this file?",MSG_QUERY))
      do_save(0);
 return true;
}

void SFrame::do_new(int)
{
  create_scintilla();
  m_sci->set_focus();
  set_filename(NONAME);
}

void SFrame::create_scintilla()
{
   if (m_sci) m_sci->show(0);
   m_sci = new ScintillaWin(this);
   m_sci->init();
   m_sci->show();
   set_client(m_sci);
   size();
   update();
}

SFrame::SFrame(const char* file)
   : TFrameWindow("USE",true), m_sci(NULL)
{
   set_background(1,1,1);
   string use_home = UCW::home_dir() + "/USE/";
   string ini_path = use_home + "use.ini";
   string ico_path = use_home + "use.ico";
   set_icon(ico_path.c_str()); 
   m_do_connect = *file == '\0';
   m_prompt_parm = false;
   m_state = FINISHED;  // for now
   m_modify_response = SAVE;
   m_show_line_nos = false;  // cd be persistent?
   m_exec_line = -1;
   
   m_ini        = new IniFile(ini_path.c_str(),false);
   m_files_used = new AutoMenu(this,m_ini,0,"Files",MAX_FILES_USED);
   m_views_open = new AutoMenu(this,m_ini,5,"Views",MAX_VIEWS_SHOWN);
   m_form_pos   = new FormOptions(this, m_ini);
   m_of         = new TOpenFile(this,"Open File",filter);
   m_ucw        = new UCW();
   m_ide        = new IDE(m_ucw);
   if (m_do_connect && m_ucw->connect()) {
     m_ucw->init(handle());
   }

 //  if (gMainFrame == NULL) {
      gMainFrame = this; 
	  m_debug_windows = new WindowList(m_ini);
      gCSW = new CallStackWindow();
      gLVW = new LocalVarsWindow();
      gWVW = new WatchVarsWindow();
	  m_debug_windows->add(gCSW);
	  m_debug_windows->add(gLVW);
      m_debug_windows->add(gWVW);
      m_debug_windows->restore();
 //  }

   // status bar
   int sb_parts[] = {80,130,-1};
   set_status_fields(sb_parts,3);

   // restore this window to its last position
   // *NB* not applicable to cloned windows!
   m_form_pos->read();

   // main menu and recently-used-file list init
   create_menu();
   m_files_used->read();

   create_scintilla();
   m_cwd = get_cwd() + "\\";
   if (*file == '\0') file = NONAME;
   set_filename(file);
}

void SFrame::cleanup()
{
  delete m_files_used;
  delete m_sci;
 // delete m_ini;
}

SFrame::~SFrame()
{
  cleanup();
}


